﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Babelfish.MetaData;
using Babelfish.CSS;
using Babelfish.JS;
using Babelfish.Node;

namespace Babelfish.HTML
{
    /// <summary>
    /// HTMLNode
    /// </summary>
    public class HTMLNode : GeneralNode, INode
    {
        /// <summary>
        /// underlying HtmlAgilityPack.HtmlNode
        /// </summary>
        public HtmlAgilityPack.HtmlNode Node;

        /// <summary>
        /// Removes this this node from its parent node.
        /// </summary>
        public void Remove(INode childNode)
        {
            if (childNode == null || childNode is HTMLNode == false)
                return;

            var node = childNode as HTMLNode;
            if (node == null)
                return;

            Node.RemoveChild(node.Node);

            _childNodes = null;
        }

        /// <summary>
        /// Gets the parent node of this node.
        /// </summary>
        /// <value>The parent node.</value>
        public INode Parent { get; set; }

        /// <summary>
        /// Gets or sets a value indicating whether [parse CSS].
        /// </summary>
        /// <value><c>true</c> if [parse CSS]; otherwise, <c>false</c>.</value>
        public bool ParseCss { get; set; }

        /// <summary>
        /// Gets or sets a value indicating whether [parse javascript].
        /// </summary>
        /// <value><c>true</c> if [parse javascript]; otherwise, <c>false</c>.</value>
        public bool ParseJavascript { get; set; }

        /// <summary>
        /// Gets the name of this node.
        /// </summary>
        /// <value>The name.</value>
        public string Name 
        {
            get
            {
                return Node.Name;
            }
        
        }

        /// <summary>
        /// Gets the value of this node.
        /// </summary>
        /// <value>The value.</value>
        public string Text
        {
            get 
            {
                var text = string.Empty;

                foreach (var child in Node.ChildNodes)
                    if (child.NodeType == HtmlAgilityPack.HtmlNodeType.Text)
                        text = text + child.InnerText;

                return text;
            }
        }

        /// <summary>
        /// Gets the metadata of this node, like html or xml comments
        /// </summary>
        /// <value></value>
        public MetaDataCollection MetaData
        {
            get
            {
                var collection = new MetaDataCollection();

                foreach (var child in Node.ChildNodes)
                    if (child.NodeType == HtmlAgilityPack.HtmlNodeType.Comment)
                        collection.Add(new GeneralMetaData{Name = child.Name, Value = child.InnerText});

                return collection;
            }
        }

        /// <summary>
        /// Gets a value indicating whether this node has a value.
        /// </summary>
        /// <value><c>true</c> if this node has a value; otherwise, <c>false</c>.</value>
        public bool HasValue
        {
            get 
            {
                return !string.IsNullOrEmpty(Node.InnerText);
            }
        }

        /// <summary>
        /// Gets the siblings of this node.
        /// </summary>
        /// <value>The sibling nodes.</value>
        public NodeCollection Siblings
        {
            get
            {
                return new NodeCollection();
            }
        }

        private bool IsStyleElement
        {
            get
            {
                return Node.Name.Trim().ToLower() == "style";
            }
        }

        private bool IsJsElement
        {
            get
            {
                return Node.Name.Trim().ToLower() == "script";
            }
        }

        NodeCollection _childNodes;
        /// <summary>
        /// Gets the child nodes of this node.
        /// </summary>
        /// <value>The child nodes.</value>
        public NodeCollection ChildNodes
        {
            get 
            {
                if(_childNodes != null)
                    return _childNodes;

                _childNodes = new NodeCollection();

                foreach (var child in Node.ChildNodes)
                    if(child != this.Node && child.NodeType == HtmlAgilityPack.HtmlNodeType.Element)
                        _childNodes.Add(new HTMLNode { Node = child, Parent = this, ParseCss = ParseCss, ParseJavascript = ParseJavascript});

                if (ParseCss && IsStyleElement)
                    _childNodes.AddRange(new CssDocument(Text).ChildNodes);

                if (ParseJavascript && IsJsElement)
                    _childNodes.AddRange(new JsDocument(Text).ChildNodes);

                _childNodes.CollectionUpdated += new NodeCollection.CollectionUpdatedHandler(_childNodes_CollectionUpdated);

                return _childNodes;
            }
        }

        /// <summary>
        /// Handles the CollectionUpdated event of the _childNodes control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="args">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        private void _childNodes_CollectionUpdated(object sender, EventArgs args)
        {
            var toremove = new List<HtmlAgilityPack.HtmlNode>();

            //remove attributes
            foreach (var child in Node.ChildNodes)
            {
                var num = this.ChildNodes.Where(node => node is HTMLNode && (node as HTMLNode).Node == child).Count();

                if (num == 0)
                    toremove.Add(child);
            }

            foreach (var node in toremove)
                Node.ChildNodes.Remove(node);
        }

        private AttributeCollection _attributes;
        /// <summary>
        /// Gets the attributes of this node.
        /// </summary>
        /// <value>The attributes.</value>
        public AttributeCollection Attributes
        {
            get 
            {
                if (_attributes != null)
                    return _attributes;

                _attributes = new AttributeCollection();

                foreach (var attribute in Node.Attributes)
                    _attributes.Add(new GeneralAttribute { Name = attribute.Name, Value = attribute.Value });

                _attributes.CollectionUpdated += new AttributeCollection.CollectionUpdatedHandler(Attributes_CollectionUpdated);

                return _attributes;
            }
        }

        /// <summary>
        /// Handles the CollectionUpdated event of the Attributes control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="args">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        private void Attributes_CollectionUpdated(object sender, EventArgs args)
        {

            var toremove = new List<HtmlAgilityPack.HtmlAttribute>();
            //remove attributes
            foreach (var attribute in Node.Attributes)
                if(!_attributes.Contains(attribute.Name))
                    toremove.Add(attribute);

            foreach (var attriburte in toremove)
                attriburte.Remove();

            //add / change attributes
            foreach (var attribute in _attributes)
            {
                if (Node.Attributes.Contains(attribute.Name))
                    Node.Attributes[attribute.Name].Value = attribute.Value;
                else
                    Node.Attributes.Add(attribute.Name, attribute.Value);
            }
        }



        /// <summary>
        /// Returns a <see cref="System.String"/> that represents this instance.
        /// </summary>
        /// <returns>
        /// A <see cref="System.String"/> that represents this instance.
        /// </returns>
        public override string ToString()
        {
            return Node.InnerHtml;
        }
    }
}
